#ifndef COMMON_REFERENCE_H
#define COMMON_REFERENCE_H

#include <typeinfo>
#include <stdexcept>

class Reference
{
    int _refCount;
    template <typename T>
    friend class RefPtr;

public:
    Reference() : _refCount(0) {}

protected:
    virtual ~Reference()
    {
        if (_refCount > 0)
        {
            // std::string refCount;
            // char buff[1024];
            // sprintf(buff,
            //        "Warning: deleting still referenced object %d of type '%s'\n"
            //        "\tthe final reference count was %d, memory corruption
            //        possible.\n", this, typeid(this).name(), _refCount );
        }
    }

    void ref() { ++_refCount; }

    void unref()
    {
        if (--_refCount <= 0)
            delete this;
    }
};

template <typename T>
class RefPtr
{
public:
    RefPtr() : _ptr(nullptr) {}

    RefPtr(T* ptr) : _ptr(ptr)
    {
        if (_ptr)
            _ptr->ref();
    }

    RefPtr(const RefPtr& r) : _ptr(r._ptr)
    {
        if (_ptr)
            _ptr->ref();
    }

    template <class Other>
    RefPtr(const RefPtr<Other>& r) : _ptr(r._ptr)
    {
        if (_ptr)
            _ptr->ref();
    }

    ~RefPtr()
    {
        if (_ptr)
            _ptr->unref();
        _ptr = nullptr;
    }

    RefPtr& operator=(T* ptr)
    {
        if (_ptr == ptr)
            return *this;
        T* tmp_ptr = _ptr;
        _ptr       = ptr;
        if (_ptr)
            _ptr->ref();
        if (tmp_ptr)
            tmp_ptr->unref();
        return *this;
    }

    RefPtr& operator=(const RefPtr& r)
    {
        assign(r);
        return *this;
    }

    template <class Other>
    RefPtr& operator=(const RefPtr<Other>& r)
    {
        assign(r);
        return *this;
    }

    T* get() const { return _ptr; }

    operator T*() const { return _ptr; }

    bool valid() const { return _ptr != nullptr; }

    bool operator!() const { return _ptr == nullptr; }

    T* operator->() const { return _ptr; }

    T& operator*() const
    {
        if (!_ptr)
            throw std::runtime_error(typeid(T).name());
        return *_ptr;
    }

private:
    template <class Other>
    void assign(const RefPtr<Other>& r)
    {
        if (_ptr == r._ptr)
            return;
        T* tmp_ptr = _ptr;
        _ptr       = r._ptr;
        if (_ptr)
            _ptr->ref();
        // unref second to prevent any deletion of any object which might
        // be referenced by the other object. i.e rp is child of the
        // original _ptr.
        if (tmp_ptr)
            tmp_ptr->unref();
    }

    T* _ptr;

    template <class Other>
    friend class RefPtr;
};
#endif